EP18. AI 에이전트와 도구
LLM 의 텍스트 생성 한계를 메모리·도구·자기성찰 패턴으로 보완해 동적으로 행동하는 시스템이 에이전틱 AI 이며, 그 핵심은 다층 메모리 설계와 LLM 친화적 도구 인터페이스입니다.
0. 사전 필수 용어 (선행지식)
본 강의 이해에 필요한 5개 용어. 모르는 항목은 AI 엔지니어링 EP0 — 선수지식 또는 아래 외부 링크 선행 학습 권장.
-
LLM (Large Language Model) — 방대한 텍스트 데이터로 학습된 확률 분포에 따라 다음 토큰을 예측·생성하는 모델. 사실·계산을 수행하는 시스템이 아니라 그럴듯한 토큰을 골라내는 텍스트 생성기. → §1·§4 에서 한계와 보완 패턴을 다룬다. (참고: Hugging Face NLP Course Ch.1)
-
프롬프트 / 컨텍스트 (Prompt / Context) — 모델 1회 호출 시 입력되는 전체 텍스트(시스템 지침 + 대화 이력 + 사용자 질의 + 도구 결과). 모델 종류에 따라 상한(예: 200K 토큰)이 있어 예산 으로 관리해야 함. → §4 Step 4, §9 한계 절에서 컨텍스트 예산 폭발 원인을 다룬다.
-
JSON Schema — JSON 데이터의 구조·타입·필수 필드를 선언적으로 기술하는 표준 명세. 최신 LLM 은 별도 학습으로 이를 이해해 구조화 출력·도구 인자 정의에 사용. → §4 Step 2, §10 최신 권장 패턴에서 사용. (참고: json-schema.org)
-
API 키 / 환경 변수 — Anthropic·OpenAI 등 LLM 공급자 인증 토큰. 코드에 박지 않고
ANTHROPIC_API_KEY같은 환경 변수로 주입. 노출 시 과금·rate limit 사고 발생. → §4 Step 1. -
RAG (Retrieval-Augmented Generation) — 외부 지식 베이스에서 관련 문서를 검색해 프롬프트에 주입하는 패턴. 본 강의에서는 항구적 메모리 의 한 종류로 분류. → §3 메모리 분류. (참고: LlamaIndex Docs)
1. 주제 정의
에이전틱 AI (Agentic AI) 는 단일 LLM 호출의 한계(추정 기반 출력·메모리 부재·외부 행동력 부재)를 메모리·도구·자기성찰·계획 수립 패턴으로 보완해 동적으로 행동하는 AI 시스템입니다. 단순 라이브러리처럼 호출되는 LLM 과 달리, 자체적으로 어떤 도구를 사용할지 결정하고, 결과를 평가해 추가 행동을 결정합니다.
핵심 아이디어: 에이전트는 LLM 1회 호출이 아니라 (질의 → 도구 사용 결정 → 도구 실행 → 결과 재투입 → 평가) 루프 위에 동작합니다.
2. 풀려는 문제
에이전틱 AI 는 다음 4가지 실무 문제를 해결합니다.
- 문제 1 (지식 한계): LLM 은 학습 시점 이후 데이터·특수 도메인 지식을 모릅니다. 외부 검색·DB 조회 도구로 보완해야 합니다.
- 문제 2 (실시간성): 시간·날씨·주가 등 실시간 값은 학습 분포에 없습니다. 전용 API 호출 도구로 가져옵니다.
- 문제 3 (정확한 계산·코딩): 확률 추론기는 산술·결정론적 연산에 약합니다. 계산기·코드 실행 도구로 위임합니다.
- 문제 4 (외부 세계 영향): 메일 발송·DB 쓰기·결제 등 부수효과는 LLM 단독 불가. 도구로 외부 시스템에 행동을 위임합니다.
💡 실무 노하우: 위 4문제는 반드시 도구로만 풀어야 하는 게 아닙니다. 정적 지식 보완은 RAG (메모리 패턴), 산술은 후처리 파이프라인으로도 가능합니다. 도구는 LLM 이 호출 여부·시점을 스스로 결정해야 할 때 만 사용하세요. 결정론적 흐름엔 직접 호출이 더 저렴하고 안정적입니다.
3. 핵심 개념·구조
에이전틱 AI 는 다음 요소로 구성됩니다.
- 메모리 (Memory): 5계층으로 분류
- 변수형 메모리: 과업 진행 상태 (예: 비행편 예약의 출발지/도착지/날짜/인원)
- 세션 메모리: 현재 대화 내 시도·실패 이력 (자기성찰 근거)
- 사용자 컨텍스트 메모리: 이전 대화·선호도
- 앱 지침 메모리: 시스템 규칙 (예: "아메리칸 항공 사용 금지")
- 항구적 메모리 (RAG): 외부 DB·문서·검색 결과
- 도구 (Tool): 외부 기능 호출 명세 (
name+description+input_schema) - 에이전트 루프 (Agent Loop): 질의 → 도구 사용 응답 → 도구 실행 → 결과 재투입 → 평가 → (만족 시) 최종 응답
- 컨트롤러 (Orchestrator): 위 흐름을 코드/그래프로 표현 (LangChain·LangGraph·n8n 같은 비주얼 빌더)
┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ 사용자 질의 │ ──▶ │ LLM │ ──▶ │ 일반 응답 ? │
└──────────────┘ │ (도구 노출) │ └──────┬───────┘
▲ └─────┬──────┘ │ 아니오
│ │ tool_use ▼
│ ▼ ┌──────────────┐
│ ┌──────────────┐ │ 외부 도구 │
└─────────────│ tool_result │◀──│ 실행 │
└──────────────┘ └──────────────┘
4. 구현 가이드 (Do It Yourself)
시작 전 (Before you begin)
이 섹션을 완료하면 Anthropic Messages API 의 도구 사용 루프를 직접 코드로 구현하고 검증할 수 있습니다.
선수 조건:
- Python 3.10+
- pip install anthropic
- Anthropic API 키 — 환경 변수 ANTHROPIC_API_KEY 설정
- §0 의 JSON Schema 개념 숙지
소요 시간: 약 20분.
Step 1 — 환경 준비
목표: API 키를 환경 변수로 안전하게 주입하고 클라이언트를 초기화합니다.
다음 코드를 agent.py 에 추가합니다.
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
이 코드는 환경 변수에서 키를 읽어 Anthropic 클라이언트를 만듭니다. 코드 안에 키를 직접 박지 않으므로 git push 사고를 방지합니다.
⚠️ 주의: API 키를 코드/문서/git 에 절대 박지 마세요. 환경 변수 또는 시크릿 매니저 사용. 노출 시 즉시 Anthropic Console 에서 revoke.
💡 실무 노하우: 키가 여러 개면 라운드로빈 풀로 rate limit 회피.
litellm의Router가 표준 구현체.📚 참고: Anthropic Python SDK README — 비동기·스트리밍·재시도 옵션 포함.
✅ 확인: Step 1 완료. python -c "import anthropic; print(anthropic.__version__)" 실행 시 버전이 출력되면 성공.
Step 2 — 도구 정의 (LLM 친화적 스키마)
목표: LLM 이 정확히 선택·호출할 수 있도록 도구 1개를 명확히 기술합니다.
다음 코드를 agent.py 에 추가합니다.
get_weather_tool = {
"name": "get_weather",
"description": "주어진 도시의 현재 기온(°C)을 반환합니다. 도시명은 한국어 또는 영어 도시명만 허용.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
name 은 LLM 이 호출에 사용하는 식별자, description 은 LLM 이 도구를 선택할 때 가장 강하게 참조하는 필드입니다. 친화적·구체적으로 작성합니다.
⚠️ 주의:
description을 모호하게 두면 도구가 무력화됩니다. "날씨 도구" (X) → "도시명을 받아 현재 기온을 반환" (O). 입력 형식·제약을 명시.💡 실무 노하우: 모델이 도구를 학습한 데이터 스타일을 참고하세요. 인자는 평문 텍스트 프리미티브 가 가장 정확도가 높고, 복잡한 중첩 JSON 은 호출 실패율을 올립니다.
📚 참고: Anthropic Tool Use Guide — 예제 도구 스키마 모음.
✅ 확인: Step 2 완료. get_weather_tool["input_schema"]["required"] 가 ["city"] 이면 성공.
Step 3 — 첫 호출과 tool_use 응답 처리
목표: 사용자 질의를 보내고, LLM 이 도구 호출을 요청하는 응답을 받습니다.
다음 코드를 agent.py 에 추가합니다.
messages = [{"role": "user", "content": "서울 지금 기온 알려줘."}]
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[get_weather_tool],
messages=messages,
)
print(resp.stop_reason, resp.content)
stop_reason 이 "tool_use" 면 LLM 이 일반 답 대신 도구 호출 응답 을 반환한 것입니다. resp.content 안에 type: "tool_use" 블록과 input (인자) 이 들어 있습니다.
⚠️ 주의: 도구 사용 시 입력·출력·결과가 모두 컨텍스트 토큰으로 누적됩니다. MCP 처럼 도구가 많고 응답이 큰 경우 컨텍스트 예산이 급격히 소모됩니다.
💡 실무 노하우: 운영 환경에선
stop_reason별 분기 + 재시도 + 토큰 사용량 로깅을 표준화하세요. OpenTelemetry + LangSmith 가 사실상 표준.📚 참고: LiteLLM tool-use 통합 가이드 — provider 무관 코드.
✅ 확인: Step 3 완료. resp.stop_reason == "tool_use" 이면 성공.
Step 4 — 도구 실행 후 결과 재투입
목표: LLM 이 요청한 도구를 실제로 실행하고 결과를 메시지 리스트에 추가해 다시 모델에 전달합니다.
다음 코드를 agent.py 에 추가합니다.
tool_use = next(b for b in resp.content if b.type == "tool_use")
result = f"{tool_use.input['city']} 현재 18°C"
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": tool_use.id, "content": result},
]})
tool_use_id 로 어느 호출에 대한 결과인지 LLM 에 명시합니다. content 는 평문 텍스트가 가장 안정적입니다.
⚠️ 주의: 실제 운영에선
result자리에 외부 API (예: OpenWeatherMap) 호출 결과가 들어갑니다. 외부 호출 실패는 반드시tool_result의is_error: True로 LLM 에 알리세요. 무응답 시 LLM 이 환각합니다.💡 실무 노하우: 도구 결과를 시멘틱 텍스트로 정리하세요. 원본 JSON 덤프보다 "서울 현재 18°C, 강수 없음" 같은 문장이 모델 이해도가 높습니다.
📚 참고: LangChain Tool Calling 표준 — provider 추상화.
✅ 확인: Step 4 완료. messages 길이가 3 (user → assistant → user) 이면 성공.
Step 5 — 최종 응답 수신 (루프 종료)
목표: 도구 결과를 포함한 전체 컨텍스트로 다시 호출해 사람이 읽을 최종 답변을 받습니다.
final = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=[get_weather_tool], messages=messages,
)
print(final.stop_reason, final.content[0].text)
예상 출력:
end_turn 서울의 현재 기온은 18°C 입니다.
stop_reason == "end_turn" 이면 LLM 이 충분한 정보를 받았다고 판단해 일반 응답을 했다는 신호입니다. 만약 다시 "tool_use" 가 오면 Step 4 를 반복합니다 — 이것이 에이전트 루프 입니다.
💡 실무 노하우: 무한 루프 방지를 위해
max_iterations(예: 10) 카운터를 두세요. LLM 이 만족 못 하면 끝없이 도구를 더 요청할 수 있습니다.📚 참고: LangGraph 의 ReAct 에이전트 패턴 — 루프·상태·체크포인트 추상화.
✅ 확인: Step 5 완료. final.stop_reason == "end_turn" 이고 본문에 기온이 포함되면 성공.
5. 적용 사례 (공신력 오픈소스)
- LangChain (github.com/langchain-ai/langchain) — Chain·Agent·Tool 추상화의 사실상 표준. Python·JS 모두 지원.
- LangGraph (github.com/langchain-ai/langgraph) — 그래프 기반 상태 머신으로 멀티 스텝 에이전트 구현. ReAct·Plan-and-Execute 예제 포함.
- LlamaIndex (github.com/run-llama/llama_index) — RAG·인덱싱 중심. 항구적 메모리(§3) 구현 표준.
- Anthropic Python SDK (github.com/anthropics/anthropic-sdk-python) — Claude API 공식 클라이언트. 본 §4 가이드의 기반.
- OpenAI Python SDK (github.com/openai/openai-python) — Function calling·Responses API 의 공식 구현.
- HuggingFace Transformers (github.com/huggingface/transformers) — 오픈 모델 추론. tool-use 학습된 오픈 모델(Llama·Qwen) 운영에 사용.
- vLLM (github.com/vllm-project/vllm) — OSS 모델 서빙 표준. OpenAI-compatible tool calling 지원.
- Ollama (github.com/ollama/ollama) — 로컬 모델 빠른 실행. 함수 호출 지원 모델 다수.
- LiteLLM (github.com/BerriAI/litellm) — 100+ provider 통합 어댑터. 키 풀·라우팅 표준.
- Spring AI (github.com/spring-projects/spring-ai) — JVM 진영 표준.
@Tool어노테이션으로 도구 정의. - LangChain4j (github.com/langchain4j/langchain4j) — Java 진영 에이전트 프레임워크.
📚 참고: 위 OSS 들은 모두 GitHub stars ≥ 5K 또는 Anthropic·OpenAI 공식 SDK 입니다.
💡 실무 노하우: 본인 스택에 맞춰 공식 SDK 우선 — wrapping 라이브러리는 디버깅 시 한 단계 더 깊은 스택을 봐야 합니다. 단, 멀티 provider 전환 가능성이 있으면 LiteLLM 같은 통합층이 ROI 좋습니다.
6. 핵심 원리
- 원리 1 (메모리 우선): 동적 학습(RL/SFT 보정) 기반 자기개선은 공수가 막대해 실무 적용 거의 불가. 대부분의 에이전트 지능은 다층 메모리를 컨텍스트에 효과적으로 배치 하는 것으로 해결됩니다. 컨텍스트 엔지니어링이 곧 에이전트 엔지니어링.
- 원리 2 (LLM 친화 인터페이스): LLM 은 본질이 NLP 엔진. 도구 설명·인자·응답이 자연어에 가까울수록 정확도가 올라갑니다. JSON 스키마는 학습되어 있지만, 값 은 평문 프리미티브가 최적.
7. 변형·확장
- 스트리밍 도구 호출:
client.messages.stream(...)으로 부분 응답을 받으며 UX 개선. 도구 호출도 partial 로 노출 가능. - 병렬 도구 호출: 최신 모델은 한 응답에 여러
tool_use블록을 동시에 생성할 수 있어 독립 도구는 병렬 실행 후 결과를 한꺼번에 재투입 가능. - 비주얼 빌더 기반: LangGraph Studio·n8n 으로 노드를 시각적으로 연결. 입력·출력이 모두 JSON 으로 통일되어 파이프라인 구성 가능.
- MCP (Model Context Protocol): 도구를 표준 프로토콜로 외부 프로세스에 분리. 단, 도구가 많으면 컨텍스트 폭발 위험 동반.
8. 다른 도구·접근과의 비교 (3-way)
- vs 단일 LLM 호출: 한 번 부르고 끝. 외부 데이터·부수효과 불가. 비용·latency 최저. 결정론 흐름 에 유리.
- vs RAG 단독: 검색 결과를 프롬프트에 주입만 함. 외부 행동 은 못 함. 정적 지식 보완에 유리, 부수효과는 별도 코드로.
- vs 도구 기반 에이전트 (본 강의): 도구로 동적 데이터·외부 행동 가능. 컨텍스트 예산·turn 수가 변동성 크다는 단점. 상호작용형 응용 에 유리.
9. 한계·트레이드오프
- 컨텍스트 예산 폭발 — 도구 사용 응답·도구 결과·재호출이 누적되어 토큰 사용량 예측 불가. MCP 같은 다도구 환경에서 특히 심함.
- 할루시네이션 (Hallucination) — 도구 결과가 비었거나 에러일 때 LLM 이 그럴듯한 가짜 답을 생성.
is_error: True명시 필수. - 턴 수 무한 가능성 — 모델이 결과를 "충분치 않다" 판단하면 추가 도구 사용을 끝없이 요청.
max_iterations가드 필수. - 도구 RnR 충돌 — 광범위 도구(웹 검색)와 전용 도구(현재 시간) 동시 제공 시 전용 도구 무력화. 도구 큐레이션 비용 큼.
- 벤더 종속 —
tool_use응답 구조가 provider 별로 다름. LiteLLM 같은 추상화층 없이는 마이그레이션 비용 큼.
10. 최신 권장 패턴 (2026-05 기준)
- Structured Output / Function Calling — JSON Schema 로 출력 형식을 강제. Anthropic 의
tool_use, OpenAI 의response_format: json_schema가 표준. - MCP (Model Context Protocol) — Anthropic 주도 도구·리소스 공유 표준. Claude Desktop·Cursor 등이 표준 클라이언트.
- Agentic 워크플로 (Plan-and-Execute, ReAct, Reflexion) — 단순 도구 루프를 넘어 계획 단계 분리·자기 비평 단계 추가. LangGraph 가 구현체 모음.
- Prompt Caching — Anthropic·OpenAI 모두 도구 정의·시스템 프롬프트를 캐시해 비용 ~90% 절감. 도구가 많은 에이전트엔 필수.
- Observability (LangSmith·Langfuse·Helicone) — 도구 호출 trace·turn 수·토큰 비용 관측 필수. 디버깅·비용 통제 핵심.
11. 메타인지 자기평가
본 강의 내용을 본인 코드/시스템에 적용 가능한지 검증.
Step 1 — 현재 상태 점검
# 현재 LLM 사용 코드가 도구를 쓰는지 grep
grep -rn "tools=\|tool_use\|function_call" your_repo/
Step 2 — 적용 가능성 평가
- LLM 응답이 단발 텍스트로 끝나지 않고 외부 데이터/행동 이 필요한가? → 도구 사용 후보
- 응답이 매번 결정론적 흐름인가? → 도구보다 직접 호출이 저렴
- 도구가 5개 이상인가? → 컨텍스트 예산·도구 충돌 검토 필요
- 메모리(세션·사용자·앱 지침)가 prompt 안에 흩어져 있는가? → 메모리 계층 분리 리팩토링 후보
Step 3 — 점진 적용
- 가장 단순한 도구 1개 (날씨·시간·계산기) 로 §4 의 5 Step 직접 실습
- 도구 정의를
description/input_schema표준화 — 인자는 프리미티브로 평탄화 max_iterations+ 토큰 로깅 +is_error처리 표준화- 도구 2~3개로 확장 후 도구 충돌 시나리오 (광범위 vs 전용) 직접 재현
- Prompt caching + 관측 도구 (LangSmith 등) 도입으로 운영화
클릭하거나 Space를 눌러 뒤집기